package com.izforge.izpack.adaptator.impl; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLFilterImpl; import javax.xml.transform.dom.DOMResult; import java.util.Queue; import java.util.Stack; import java.util.LinkedList; /** * A custom SAX XML filter, used to add line numbers to a DOM document. * This filter stores line numbers while parsing, and the applyLN method set * line numbers on the result. * Line numbers are stored in the user data of the Element, * so require Java 5 (DOM 3) or higher. * * @author Anthonin Bonnefoy * @author David Duponchel */ public class LineNumberFilter extends XMLFilterImpl { /** * a queue to store line numbers while parsing. */ private Queue<Integer> lnQueue; /** * The locator given while parsing. */ private Locator locator; public LineNumberFilter(XMLReader xmlReader) { super(xmlReader); } @Override public void startDocument() throws SAXException { super.startDocument(); lnQueue = new LinkedList<Integer>(); } @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { super.startElement(uri, localName, qName, atts); lnQueue.add(locator.getLineNumber()); } @Override public void setDocumentLocator(Locator locator) { super.setDocumentLocator(locator); this.locator = locator; } /** * Return the first element found from the given Node. * * @param elt The Node start point. * * @return The first Element found, or null. */ private Element getFirstFoundElement(Node elt) { while (elt != null && elt.getNodeType() != Node.ELEMENT_NODE) { elt = elt.getNextSibling(); } return (Element) elt; } /** * Return the next element sibling found from the given Node. * * @param elt The Node start point. * * @return The next sibling Element, or null if nto found. */ private Element getNextSibling(Node elt) { return getFirstFoundElement(elt.getNextSibling()); } /** * Return the first element child found from the given Node. * * @param elt The Node start point. * * @return The first child Element found, or null. */ private Element getFirstChild(Node elt) { return getFirstFoundElement(elt.getFirstChild()); } /** * Returns whether the given node has any element children. * * @param elt The node to check. * * @return Returns <code>true</code> if this node has any children, * <code>false</code> otherwise. */ private boolean hasChildElements(Node elt) { return getFirstChild(elt) != null; } /** * Apply a line number on the given element. * We assume that the line number queue has been correctly filled, and that * the current DOM tree correspond to the parsed XML. * * @param elt the element to apply the line number. */ private void applyLN(Element elt) { elt.setUserData("ln", lnQueue.poll(), null); } /** * Apply line numbers stored by a parse using this object on the xml elements. * * @param result The result of the parse. */ public void applyLN(DOMResult result) { Element elt = getFirstChild(result.getNode()); boolean end = false; Stack<Element> stack = new Stack<Element>(); while (!end) { if (hasChildElements(elt)) { // not a leaf stack.push(elt); applyLN(elt); elt = getFirstChild(elt); // go down } else { // a leaf applyLN(elt); Element sibling = getNextSibling(elt); if (sibling != null) { // has a sibling elt = sibling; } else { // no sibling do { if (stack.isEmpty()) { end = true; } else { elt = stack.pop(); elt = getNextSibling(elt); } } while (!end && elt == null); } } } } }